﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Collections.ObjectModel;

namespace Summerset.Isbndb
{

    /// <summary>
    /// Base class for all requests generated for any of the ISBNdb.com collections.
    /// </summary>
    public abstract class RequestBase
    {
        #region Constructor + Methods

        /// <summary>
        /// Initializes a new instance of the RequestBase.
        /// </summary>
        /// <param name="indexName">The index which will be queried with the search terms.</param>
        /// <param name="searchTerms">The search terms that will be used to query the collection.</param>
        /// <param name="additionalResultFields">Additional details to request from the API.</param>
        public RequestBase(string indexName, string searchTerms, params string[] additionalResultFields) 
        {
            // Set all the required properties
            this.IndexName              = indexName;
            this.SearchTerms            = searchTerms;
            this.AdditionalResultFields = additionalResultFields;            
        }

        /// <summary>
        /// Gets the full request URI for a request. 
        /// </summary>
        /// <param name="pageNumber">The page number that will be requested.  This is a 1-based argument.</param>
        /// <param name="api">A reference to the <see cref="Isbndb"/> instance that this request will execute against.</param>
        /// <returns>A Uri for the request.  Each GetFullRequestUri will return the underlying data for a single page.</returns>
        public Uri GetRequestFullUri(int pageNumber, Isbndb api)
        {            
            var sb = new StringBuilder();

            // 1 - Add the access key            
            sb.AppendFormat("access_key={0}", api.ApiKey);

            // 2 - add the search request
            sb.AppendFormat("&index1={0}", this.IndexName);
            sb.AppendFormat("&value1={0}", Uri.EscapeDataString(this.SearchTerms));

            // 3 - finally, add the results argument.  this is a CSV argument that represents additional data that can be requested 
            sb.AppendFormat("&results={0}", "keystats"); // We're always going to ask that the keystats be returned
            if (AdditionalResultFields.Length > 0)
                sb.AppendFormat(",{0}", string.Join(",", this.AdditionalResultFields)); // we've already added the Results= arg, so we just need to add an additional comma and more results fields

            // 4 - add the page number
            sb.AppendFormat("&page_number={0}", pageNumber);

            // Create the new Uri
            var builder = new UriBuilder(RequestBaseUri);
            builder.Query = (sb.ToString());

            return builder.Uri;

        }
        #endregion
        #region Properties

        /// <summary>
        /// A string that represents that value that will be provided for the index1 argument of the collection endpoint.
        /// </summary>
        /// <exception cref="ArgumentException">Throw if any of the elements provided cannot be found in the array returned GetValidIndexArguments()</exception>
        public string IndexName
        {
            get { return _indexName; }
            private set
            {                
                // Make sure the index is valid for this gateway.
                if (!this.GetValidIndexNames().Contains(value)) 
                    throw new ArgumentException(string.Format("The provided Index Name: {0} is not valid.", value));

                _indexName = value;                
            }
        }
        string _indexName = string.Empty;

        /// <summary>
        /// Gets or sets the search terms.
        /// </summary>
        /// <value>
        /// The terms that will be searched according to the selected index.
        /// </value>
        /// <remarks>
        /// The string provided here is not and should not be escaped.  
        /// Escaping this string is performed in the GetFullRequestUri() method.
        /// </remarks>
        public string SearchTerms
        {
            get { return _queryTerms; }
            private set { if (value != SearchTerms) _queryTerms = value; }
        }
        string _queryTerms = string.Empty;

        /// <summary>
        /// Extra field that will be provided in this message response.
        /// </summary>
        /// <exception cref="ArgumentException">Throw if any of the elements provided cannot be found in the array returned GetValidAdditionalResultFields()</exception>
        public string[] AdditionalResultFields
        {
            get { return _additionalResultFields; }
            private set 
            { 
                /* We need to validate each element in the provided array 
                 * is a valid argument for the isbndb collection that 
                 * we're going to be requesting it from.
                 */

                // get all valid arguments
                var validFields = this.GetValidAdditionalResultFields(); 

                // loop through ever element in the array that's being provided by 'value'
                foreach (var fieldName in value)
                {
                    // throw an argument exception if the field is invalid.
                    if (!validFields.Contains(fieldName)) 
                        throw new ArgumentException(string.Format("{0} is not a valid field for Additional Result Fields.", fieldName));
                }

                // instantiate the backing field to have the same number of elements, and then copy the elements from the original array
                _additionalResultFields = new string[value.Length];
                value.CopyTo(_additionalResultFields, 0);
                
            }
        }
        string[] _additionalResultFields;

        #endregion
        #region Abstract Properties

        /// <summary>
        /// When overriden in a derrived class, gets the base URI for all requests against this collection.
        /// </summary>
        public abstract Uri RequestBaseUri { get; }

        /// <summary>
        /// When overridden in a derrived class, gets all the valid indexes for this collection.
        /// </summary>
        protected abstract Func<string[]> GetValidIndexNames { get; }

        /// <summary>
        /// When overridden in a derrived class, gets all the valid additional result fields for this collection.
        /// </summary>
        protected abstract Func<string[]> GetValidAdditionalResultFields { get; }

        #endregion
    }
}
